<?php
// +----------------------------------------------------------------------
// | 更新程序
// +----------------------------------------------------------------------
// | Author: wm
// +----------------------------------------------------------------------

namespace test;
class Upgrade
{
    private $update_log = "/tmp/web/update_log.log"; //系统升级日志
    private $return_log = "/tmp/web/return_log.log"; //系统回滚日志
    private $progress_log = "/tmp/web/progress_log.log"; //记录进度
    private $root_dir = "/Users/feng/Documents/work/test"; //站点代码的根目录
    private $aFile = ["log", "runtime"];//忽略文件夹相对路径
    private $backup_dir = "/tmp/web/backup_dir";//备份目录
    private $upload_dir = "/tmp/web/upload_dir";//升级包目录
    private $sys_version_num = '1.0.0';//当前系统的版本 这个在实际应用中应该是由数据库获取得到的，这里只是举个例子

    /** 展示升级界面 */
    public function index()
    {
        include("update.html");
    }

    /**
     * 处理升级包上传
     */
    public function upload()
    {
        $params = $_POST;
        if ($_FILES) {
            $name = $_FILES['file']['tmp_name'];
            if (!$name || !is_uploaded_file($name)) {
                echo json_encode(["status" => 0, "msg" => "请上传升级包文件"]);
                die;
            }
        }
        //校验后缀
        $astr = explode('.', $name);
        $ext = array_pop($astr);
        if ($ext != 'zip') {
            echo json_encode(["status" => 0, "msg" => "请上传文件格式不对"]);
            die;
        }

        //对比版本号
        $astr = explode('_', $name);
        $version_num = str_replace(".zip", '', array_pop($astr));
        if (!$version_num) {
            echo json_encode(["status" => 0, "msg" => "获取版本号失败"]);
            die;
        }
        //对比
        if (!$this->compare_version($version_num)) {
            echo json_encode(["status" => 0, "msg" => "不能升级低版本的"]);
            die;
        }
        $package_name = $this->upload_dir . '/' . $version_num . '.zip';
        if (!move_uploaded_file($name, $package_name)) {
            echo json_encode(["status" => 0, "msg" => "上传文件失败"]);
            die;
        }

        //记录下日志
        $this->save_log("上传升级包成功！");
        $this->update_progress("20%");
        //备份code
        $result = $this->backup_code();
        if (!$result) {
            $this->save_log("备份失败！");
            echo json_encode(["status" => 0, "msg" => "备份失败"]);
            die;
        }
        $this->update_progress("30%");
        //执行升级
        $this->execute_update($package_name);
    }

    /**
     * 升级操作
     * @return [type] [description]
     */
    private function execute_update($package_name)
    {
        //解压 如何使用zip加密压缩，这里解压缩的时候注意要解密
        exec(" cd $this->upload_dir && unzip $package_name ");
        $package_name = str_replace(".zip", "", $package_name);
        if (!is_dir($package_name)) {
            $this->save_log("解压失败");
            echo json_encode(["status" => 0, "msg" => "解压失败"]);
            die;
        }
        $this->update_progress("50%");
        //升级mysql
        if (file_exists($this->upload_dir . '/' . $package_name . "/mysql/mysql_update.sql")) {
            $result = $this->database_operation($this->upload_dir . '/' . $package_name . "/mysql/mysql_update.sql");
            if (!$result['status']) {
                echo json_encode($result);
                die;
            }
        }
        $this->update_progress("70%");
        //升级PHP
        if (is_dir($this->upload_dir . '/' . $package_name . '/php')) {
            exec("cd {$this->upload_dir}/{$package_name}/php && cp -rf ./* $this->root_dir ", $mdata, $status);
            if ($status != 0) {
                $this->save_log("php更新失败");
                //数据库回滚
                if (file_exists($this->upload_dir . '/' . $package_name . "/mysql/mysql_rockback.sql")) {
                    $this->save_log("数据库回滚");
                    $this->database_operation($this->upload_dir . '/' . $package_name . "/mysql/mysql_rockback.sql");

                }
                //php代码回滚
                $cmd = "cp -rf " . $this->backup_dir . "/" . $this->sys_version_num . '/' . basename($this->root_dir) . "/* " . $this->root_dir;
                exec($cmd, $mdata, $status);
                $this->save_log("php回滚");
                echo json_encode(["status" => 0, "msg" => "php更新失败"]);
                die;
            }
        }
        //把解压的升级包清除
        exec("rm -rf $this->upload_dir/$package_name ");

        $this->update_progress("100%");
        //更新系统的版本号了
        //更新php的版本号了(应该跟svn／git的版本号一致)
        //更新数据库的版本号了(应该跟svn／git的版本号一致)
        echo json_encode(["status" => 1, "msg" => "升级成功"]);
        die;
    }

    /**
     * 比较代码版本
     * @return [type] [description]
     */
    private function compare_version($version_num = '1.0.0')
    {
        return version_compare($version_num, $this->sys_version_num, '>');
    }

    /**
     * 备份代码
     */
    private function backup_code()
    {
        //rsync 要确定系统是否已经安装
        $cmd = "cd $this->root_dir && cd ..  && rsync -av ";
        foreach ($this->aFile as $key => $value) {
            $cmd . "--exclude " . basename($this->root_dir) . "/" . $value . " ";
        }
        $cmd .= basename($this->root_dir) . " " . $this->backup_dir . "/" . $this->sys_version_num;
        exec($cmd, $mdata, $status);
        if ($status != 0) {
            return false;
        }
        //这里还可以对备份的文件进行压缩
        return true;
    }

    /**
     * 数据库操作
     */
    public function database_operation($file)
    {
        $mysqli = new mysqli("localhost", "root", "root", "test");
        if ($mysqli->connect_errno) {
            return ["status" => 0, "msg" => "Connect failed:" . $mysqli->connect_error];
        }
        $sql = file_get_contents($file);
        $mysqli->multi_query($sql);
        return ["status" => 1, "msg" => "数据库操作OK"];
    }

    /**
     * 返回系统升级的进度
     */
    public function update_progress($progress)
    {
        exec(" echo '" . $progress . "' > $this->progress_log ");
    }

    /**
     * 记录日志
     */
    public function save_log($msg, $action = "update")
    {
        $log = $action == "update" ? $this->update_log : $this->return_log;
        $msg .= date("Y-m-d H:i:s") . ":" . $msg . "\n";
        exec(" echo '" . $msg . "' >>  $log ");
    }

}